﻿#region Includes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK.Graphics.OpenGL;
#endregion

namespace GingerGL.Graphics
{
    //------------------------------------------------------------//
    /// <summary>
    /// Stores vertices in a buffer with a specific vertex format.
    /// </summary>
    public class VertexBuffer : IDisposable
    {
        //--------------------------------------------------------//
        #region Variables
        /// <summary>
        /// Gets TRUE if Buffer is loaded into memory
        /// </summary>
        public bool IsCommited { get; private set; }
        /// <summary>
        /// Gets OpenGL Vertex Buffer ID
        /// </summary>
        public int VertexBufferId { get; private set; }
        private List<Vertex> mVertexBuffer;
        private int mCount;
        #endregion
        //--------------------------------------------------------//
        #region Properties
        /// <summary>
        /// Gets the count vertices in the buffer.
        /// </summary>
        /// <value>Count of vertices.</value>
        public int Count { get { return this.mCount - 1; } }
        /// <summary>
        /// Indexor
        /// </summary>
        public Vertex this[int ndex] { get { return mVertexBuffer[ndex]; } }
        #endregion
        //--------------------------------------------------------//
        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="VertexBuffer"/> class.
        /// </summary>
        /// <param name="vertexFormat">Vertex format used by the vertex buffer.</param>
        public VertexBuffer()
        {
            //Init private members
            this.mVertexBuffer = new List<Vertex>();
            this.mCount = 0;
            this.VertexBufferId = -1;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="VertexBuffer"/> class.
        /// </summary>
        public VertexBuffer(Vertex[] vertices) : this() { this.Initialize(vertices); }
        #endregion
        //--------------------------------------------------------//
        #region Methods
        /// <summary>
        /// Initializes a new vertex buffer and commits to video memory
        /// </summary>
        private void Initialize(Vertex[] vertices) { this.Write(vertices); this.Commit(); }
        /// <summary>
        /// Clear Local Cache
        /// </summary>
        public void Clear() { this.mVertexBuffer.Clear(); mVertexBuffer = null; }
        /// <summary>
        /// Delete Vertex Data in Video Memory
        /// </summary>
        public void Invalidate()
        {
            int id = VertexBufferId;
            if (VertexBufferId != -1) GL.DeleteBuffers(1, ref id);
            IsCommited = false;
            mCount = 0;
        }
        /// <summary>
        /// Commits Vertex Data to Video Memory
        /// </summary>
        public void Commit()
        {
            if (mVertexBuffer == null) 
                throw new 
                    ArgumentNullException("Buffer is null");    //dblcheck
            if (mVertexBuffer.Count < 1) return;                //dblcheck

            int id;
            GL.GenBuffers(1, out id);                           //create buffers
            VertexBufferId = id;
            int bufferSize;                                     //passing in the requests buffer and will save the save VBO information
            GL.BindBuffer(                                      // Bind current context to Array Buffer ID
                BufferTarget.ArrayBuffer, 
                VertexBufferId);
            GL.BufferData(                                      // Send data to buffer
                BufferTarget.ArrayBuffer,
                (IntPtr)(mVertexBuffer.Count * Vertex.SizeInBytes),
                mVertexBuffer.ToArray(),
                BufferUsageHint.StaticDraw);
            GL.GetBufferParameter(                              // Validate that the buffer is the correct size
                BufferTarget.ArrayBuffer, 
                BufferParameterName.BufferSize, 
                out bufferSize);

            if (mVertexBuffer.Count * Vertex.SizeInBytes != bufferSize)
                throw new ApplicationException("Element array not uploaded correctly");

            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);  //Clear the buffer Binding
            
            this.IsCommited = true;                             //save to memory
            this.mCount = mVertexBuffer.Count;                  //remember the count
        }
        /// <summary>
        /// Gets a vertex element from the vertex buffer at the specified index with the specified elementName.
        /// </summary>
        /// <typeparam name="T">Type of the vertex element to retrieve.</typeparam>
        /// <param name="vertexIndex">Index of the vertex in the vertex buffer.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <returns>Value of the vertex element.</returns>
        public Vertex GetVertexElement(int vertexIndex) { return mVertexBuffer[vertexIndex]; }
        /// <summary>
        /// Writes the value in the vertex buffer.
        /// </summary>
        /// <typeparam name="T">Type of the value to write.</typeparam>
        /// <param name="value">Value to write to the vertex buffer.</param>
        public void Write(Vertex value) { mVertexBuffer.Add(value); }
        /// <summary>
        /// Writes the value in the vertex buffer.
        /// </summary>
        /// <typeparam name="T">Type of the value to write.</typeparam>
        /// <param name="value">Value to write to the vertex buffer.</param>
        public void Write(Vertex[] value) { mVertexBuffer.AddRange(value); }
        /// <summary>
        /// Disposes of this class and video memory
        /// </summary>
        public void Dispose() { this.Invalidate(); this.Clear(); }
        #endregion
        //--------------------------------------------------------//
    }
    //------------------------------------------------------------//
}
